﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RemoteTestRunner.Contracts;
using System.IO;
using System.Reflection;
using System.Security.Policy;
using System.Security;

namespace RemoteTestRunner.BusinessLogic
{
    [Serializable]
    public class OriginalMetadataGrabber : IAssemblyMetadataRetriever 
    {
        public List<Contracts.TestClass> GetTestMethods(Contracts.Test test)
        {

            List<TestClass> results;
            if (!File.Exists(test.AssemblyPath))
            {
                var errorClass = new TestClass { Name = "Assembly not found" };

                results = new List<TestClass> { errorClass };

                return results;
            }

            var thisAssembly = Assembly.GetExecutingAssembly();

            // Configure AppDomainSetup
            var domainSetup = new AppDomainSetup();
            var uriPath = new Uri(thisAssembly.CodeBase);
            domainSetup.ApplicationBase = Path.GetDirectoryName(uriPath.AbsolutePath);
            domainSetup.PrivateBinPath = Path.GetDirectoryName(uriPath.AbsolutePath);
            //domainSetup.DisallowBindingRedirects = false;
            //domainSetup.DisallowCodeDownload = true;
            domainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

            // Configure the AppDomain
            EvidenceBase[] evidence = { new Zone(SecurityZone.MyComputer) };
            var trustedEvidence = new Evidence(evidence, null);
            var testMethodsDomain = AppDomain.CreateDomain("GetTestMethods", trustedEvidence, domainSetup);

            try
            {

                // Get the test methods from the AppDomain
                var rtrLib = (OriginalMetadataGrabber)testMethodsDomain.CreateInstanceAndUnwrap(thisAssembly.FullName, GetType().FullName);

                results = rtrLib.InternalGetTestMethods(test);

            }
            finally
            {
                // Cleanup
                AppDomain.Unload(testMethodsDomain);
            }

            return results;
        }


        /// <summary>
        ///   Gets a two level heirarchy of Classes decorated with the TestClassAttribute and Methods decorated with the TestMethodAttribute.
        /// </summary>
        /// <param name = "test"></param>
        /// <returns></returns>
        internal List<TestClass> InternalGetTestMethods(Test test)
        {
            //
            // Build a two level heirarchy of Classes and Methods
            // Class 1
            // --- Method 1
            // --- Methid 2
            // Class 2
            // --- Method 1
            // --- Methid 2
            // etc.


            var targetAssembly = Assembly.LoadFrom(test.AssemblyPath);
            var msTestAssembly =
                Assembly.Load(
                    "Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL");

            // TODO: convert to use ReflectionOnly this requires less permissions then the method below. 
            // This in turn will not require the calling app to have Code Access Security setup to Full Trust.
            // This requires calls to CustomAttributeData instead of GetCustomAttributes.
            ////System.AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
            ////System.Reflection.Assembly targetAssembly = System.Reflection.Assembly.ReflectionOnlyLoad(test.AssemblyPath);
            ////System.Reflection.Assembly targetAssembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(test.AssemblyPath);

            Func<Type, bool> hasTestClassAttribute =
                t => t.GetCustomAttributes(
                    msTestAssembly.GetType("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"),
                    true).Length > 0;

            Func<MethodInfo, bool> hasTestMethodAttribute = m => m.GetCustomAttributes(
                msTestAssembly.GetType(
                    "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute"),
                true).Length > 0;

            Func<MethodInfo, bool> hasDataSourceAttribute = m => m.GetCustomAttributes(
                msTestAssembly.GetType(
                    "Microsoft.VisualStudio.TestTools.UnitTesting.DataSourceAttribute"),
                true).Length > 0;

            // Get all the classes from the assembly with the [TestClass] attribute
            var classes = from type in targetAssembly.GetTypes()
                          where hasTestClassAttribute(type)
                          select type;

            var results = new List<TestClass>();

            if (classes.Count() == 0)
            {
                var errorClass = new TestClass
                {
                    Name = "No classes in assembly are marked with TestClassAttribute",
                    IsTestable = false
                };
                results.Add(errorClass);

                var errorMethod = new TestMethod
                {
                    Name = "No methods in class are marked with the TestMethodAttribute",
                    IsTestable = false
                };
                errorClass.TestMethods.Add(errorMethod);

                return results;
            }

            // Loop through identified classes and find the [TestMethod] methods.
            foreach (var types in classes)
            {
                var testClass = new TestClass
                {
                    Name = types.Name,
                    IsTestable = true
                };

                results.Add(testClass);

                var testMethods =
                    from testMethod in types.GetMethods()
                    where hasTestMethodAttribute(testMethod)
                    select new TestMethod
                    {
                        Name = testMethod.Name,
                        // Data driven tests have an additional attribute
                        DataDriven = hasDataSourceAttribute(testMethod),
                        IsTestable = true
                    };

                if (testMethods.Count() == 0)
                {
                    var errorMethod = new TestMethod
                    {
                        Name = "No methods in class are marked with the TestMethodAttribute",
                        IsTestable = false
                    };
                    testClass.TestMethods.Add(errorMethod);
                }
                else
                {
                    foreach (var method in testMethods)
                    {
                        testClass.TestMethods.Add(method);
                    }
                }
            }

            return results;
        }
    }
}
